﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using LoungeRepo.Contracts.Core;
using LoungeRepo.Contracts.EntityCache;
using LoungeRepo.Contracts.EntityStore;
using LoungeRepo.Contracts.Normalizer;
using LoungeRepo.Contracts.Serialization;
using LoungeRepo.DataStructures;
using LoungeRepo.DispatcherEntityStore;
using LoungeRepo.EntityCache;
using LoungeRepo.EntityStore;
using LoungeRepo.Extensions;
using LoungeRepo.Normalizer;
using LoungeRepo.Serialization;

namespace LoungeRepo.Core
{
    public partial class LoungeRepository : ILoungeRepository
    {
        private readonly string rootFolderPath;

        private readonly IEntityNormalizer normalizer;
        private readonly INormalizedEntitySerializer serializer;
        private readonly IDispatcherEntityStore dispatcher;
        private readonly IEntityStore filesys;
        private readonly IEntityCache cache;


        public LoungeRepository() : this(new LoungeRepoConfig()) {}
        public LoungeRepository(string rootFolderPath) : this(new LoungeRepoConfig { RootFolderPath = rootFolderPath }) { }
        public LoungeRepository(LoungeRepoConfig config)
        {
            Trace.TraceInformation("LoungeRepo.Core.LoungeRepository.Ctor('{0}')", config.RootFolderPath);

            this.rootFolderPath = Path.GetFullPath(config.RootFolderPath);

            this.normalizer = new EntityNormalizer(OnResolveEntityReferenceRequest,
                                                   OnEntityReportedDuringDeNormalization);
            this.serializer = new BinarySerializer();
            this.dispatcher = new RegexDispatcherEntityStore();
            this.dispatcher.RegisterDefaultPartitionEntityStore(new FilesystemEntityStore(this.rootFolderPath));
            this.filesys = this.dispatcher;
            this.cache = new SecondLevelCache();
        }


        public void Store(object nonEntityRoot, string id) { Store(nonEntityRoot, id, ""); }
        public void Store(object nonEntityRoot, string id, string partition)
        {
            var entity = new WrapperEntity {Id = id, Partition = partition, NonEntityObject = nonEntityRoot};
            Store(entity);
        }
        public void Store(object entityRoot)
        {
            ILoungeRepoEntityIdentity entityIdentity;
            if (this.normalizer.TryExtractEntityIdentity(entityRoot, out entityIdentity))
            {
                var normalizedEntities = normalizer.Normalize(entityRoot);
                PersistNormalizedEntities(normalizedEntities);
            }
            else
                throw new ArgumentException("Object passed to Store() needs to be an entity or an explicit id must accompany it!");
        }


        public T Load<T>(string id) { return Load<T>(id, ""); }
        public T Load<T>(string id, string partition) { return (T) Load(id, partition); }
        public object Load(string id, string partition)
        {
            object entity;
            if (this.cache.TryGetByKey(id, partition, out entity))
            {
                return entity;
            }

            INormalizedEntity normalizedEntity = DepersistNormalizedEntity(id, partition);

            entity = this.normalizer.Denormalize(normalizedEntity);

            if (entity is WrapperEntity)
                entity = (entity as WrapperEntity).NonEntityObject;

            return entity;
        }


        public void Delete(string id) { Delete(id, "");  }
        public void Delete(string id, string partition)
        {
            this.cache.Evict(id, partition);
            this.filesys.Delete(id, partition);
        }


        public IEnumerable<T> GetExtent<T>()
        {
            return this.cache.GetByType<T>();
        }


        public string RootFolderPath
        {
            get
            {
                return this.rootFolderPath;
            }
        }


        public void RegisterPartitionEntityStore(string partitionPattern, string partitionPath)
        {
            this.RegisterPartitionEntityStore(partitionPattern, 
                                              new FilesystemEntityStore(partitionPath));
        }

        public void RegisterPartitionEntityStore(string partitionPattern, IEntityStore partitionEntityStore)
        {
            this.RegisterPartitionEntityStore(new Regex(partitionPattern.ToRegexPattern(), 
                                                        RegexOptions.Compiled | RegexOptions.IgnoreCase), 
                                              partitionEntityStore);
        }

        public void RegisterPartitionEntityStore(Regex partitionRegex, IEntityStore partitionEntityStore)
        {
            this.dispatcher.RegisterPartitionEntityStore(partitionRegex, 
                                                         partitionEntityStore);
        }


        private object OnResolveEntityReferenceRequest(ILoungeRepoEntityIdentity entityReference)
        {
            object entity;
            if (this.cache.TryGetByKey(entityReference.Id, entityReference.Partition, out entity))
            {
                return entity;
            }

            return DepersistNormalizedEntity(entityReference.Id, entityReference.Partition);
        }


        private void OnEntityReportedDuringDeNormalization(object entity, ILoungeRepoEntityIdentity entityIdentity)
        {
            if (entity is WrapperEntity) entity = (entity as WrapperEntity).NonEntityObject;
            this.cache.Register(entity, entityIdentity.Id, entityIdentity.Partition);
        }


        #region Implementation of IDisposable
        public void Dispose()
        {
            this.filesys.Dispose();
            this.dispatcher.Dispose();
            Trace.TraceInformation("LoungeRepo.Core.LoungeRepository.Dispose()");            
        }
        #endregion
    }
}
